﻿using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Model.DataManagement;
using Model.Entity.Members;

namespace LibraryManagerTests.Model.DataManagement
{
    [TestClass]
    public class MemberManagerTests
    {

        class TestableMemberManager : MemberManager
        {
            public static Int32 FirstId { get { return FIRST_ID; } }

            public IList<Member> Entities { get { return _entities; } }

            public IList<Member> Edited { get { return _edited; } }

            public IList<Member> New { get { return _new; } }

            public IList<Member> Deleted { get { return _deleted; } }

            public IList<Member> Managed { get { return _managed; } }

        }

        /// <summary>
        /// Tests MemberManager.CanSave(), the Member validation method.
        /// </summary>
        [TestMethod]
        public void TestCanSave()
        {

            // obtaining references for less text
            ushort nameMax = MemberManager.MAX_NAME_LENGTH;
            string nameErr = MemberManager.NAME_LEN_ERROR_HU;
            ushort addrMax = MemberManager.MAX_ADDR_LENGTH;
            string addrErr = MemberManager.ADDR_LEN_ERROR_HU;
            ushort contMax = MemberManager.MAX_CONTACT_LENGTH;
            string contErr = MemberManager.CONTACT_LEN_ERROR_HU;

            MemberManager mm = new MemberManager();
            LocalStudent s = new LocalStudent(1);
            Messages msg;

            //
            // name length validation
            //

            // valid names
            for (int i = 0; i <= nameMax; i++)
            {
                s.Name = string.Concat(Enumerable.Repeat("x", i));
                msg = mm.CanSave(s);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid names
            for (int i = nameMax + 1; i < nameMax * 2; i++)
            {
                s.Name = string.Concat(Enumerable.Repeat("x", i));
                msg = mm.CanSave(s);
                string expectedMsg = string.Format(nameErr, i, nameMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            //
            // address length validation
            //
            s.Name = "";

            // valid addresses
            for (int i = 0; i <= addrMax; i++)
            {
                s.Address = string.Concat(Enumerable.Repeat("x", i));
                msg = mm.CanSave(s);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid addresses
            for (int i = addrMax + 1; i < addrMax * 2; i++)
            {
                s.Address = string.Concat(Enumerable.Repeat("x", i));
                msg = mm.CanSave(s);
                string expectedMsg = string.Format(addrErr, i, addrMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            //
            // contact length validation
            //
            s.Address = "";

            // valid contacts
            for (int i = 0; i <= contMax; i++)
            {
                s.Contact = string.Concat(Enumerable.Repeat("x", i));
                msg = mm.CanSave(s);

                Assert.AreEqual(0, msg.MessageList.Count);
            }

            // invalid contacts
            for (int i = contMax + 1; i < contMax * 2; i++)
            {
                s.Contact = string.Concat(Enumerable.Repeat("x", i));
                msg = mm.CanSave(s);
                string expectedMsg = string.Format(contErr, i, contMax);

                Assert.AreEqual(1, msg.MessageList.Count);
                Assert.AreEqual(expectedMsg, msg.MessageList[0]);
            }

            //
            // compound length validation
            //

            ushort tests = 200; // the more the better?
            Random r = new Random();
            ushort nameLen;
            string nameMsg;
            ushort addrLen;
            string addrMsg;
            ushort contLen;
            string contMsg;
            ushort count;

            ushort invalidNames = 0;
            ushort invalidAddresses = 0;
            ushort invalidContacts = 0;
            for (int i = 0; i < tests; i++)
            {

                // generate random length name
                nameLen = (ushort) r.Next(nameMax * 2);
                s.Name = string.Concat(Enumerable.Repeat("x", nameLen));

                // generate random length address
                addrLen = (ushort) r.Next(addrMax * 2);
                s.Address = string.Concat(Enumerable.Repeat("x", addrLen));

                // generate random length contact
                contLen = (ushort) r.Next(contMax * 2);
                s.Contact = string.Concat(Enumerable.Repeat("x", contLen));

                msg = mm.CanSave(s);
                count = 0;

                // check name validation
                nameMsg = string.Format(nameErr, nameLen, nameMax);
                if (nameLen > nameMax)
                {
                    Assert.IsTrue(msg.MessageList.Contains(nameMsg));
                    count++;
                    invalidNames++;
                }
                else
                {
                    Assert.IsFalse(msg.MessageList.Contains(nameMsg));
                }

                // check address validation
                addrMsg = string.Format(addrErr, addrLen, addrMax);
                if (addrLen > addrMax)
                {
                    Assert.IsTrue(msg.MessageList.Contains(addrMsg));
                    count++;
                    invalidAddresses++;
                }
                else
                {
                    Assert.IsFalse(msg.MessageList.Contains(addrMsg));
                }

                // check contact validation
                contMsg = string.Format(contErr, contLen, contMax);
                if (contLen > contMax)
                {
                    Assert.IsTrue(msg.MessageList.Contains(contMsg));
                    count++;
                    invalidContacts++;
                }
                else
                {
                    Assert.IsFalse(msg.MessageList.Contains(contMsg));
                }

                Assert.AreEqual(count, msg.MessageList.Count);
            }

            // fail the test if there was a non-tested case
            Assert.IsTrue(invalidNames > 0 && invalidNames < tests);
            Assert.IsTrue(invalidAddresses > 0 && invalidAddresses < tests);
            Assert.IsTrue(invalidContacts > 0 && invalidContacts < tests);

        }

        /// <summary>
        /// Tests BorrowingManager.Marshal(), BorrowingManager.Unmarshal().
        /// </summary>
        [TestMethod]
        public void TestMarshalUnmarshal()
        {
            int tests = 1000;

            MemberManager lm = new MemberManager();
            Member l;
            Member u; // unmarshalled
            Random r = new Random();
            RandomUnicodeString randUniStr = new RandomUnicodeString();

            /*
             * Tests by fields
             */

            // all fields
            Int32 id;
            string name;
            var maxName = MemberManager.MAX_NAME_LENGTH;
            string address;
            var maxAddress = MemberManager.MAX_ADDR_LENGTH;
            string contact;
            var maxContact = MemberManager.MAX_CONTACT_LENGTH;

            byte[] m; // marshalled
            string tName;
            l = new Other();
            for (int i = 0; i < tests; i++)
            {
                id = r.Next(TestableMemberManager.FirstId, Int32.MaxValue);
                name = randUniStr.GetString(r.Next(maxName * 2));
                address = randUniStr.GetString(r.Next(maxAddress * 2));
                contact  = randUniStr.GetString(r.Next(maxContact * 2));

                switch (i % 4 + 1)
                {
                    case 1:
                        l = new LocalStudent(id, name, address, contact);
                        break;
                    case 2:
                        l = new LocalTeacher(id, name, address, contact);
                        break;
                    case 3:
                        l = new ForeignUniMember(id, name, address, contact);
                        break;
                    case 4:
                        l = new Other(id, name, address, contact);
                        break;
                }
                tName = l.GetType().Name;
                m = lm.Marshal(l);
                u = lm.Unmarshal(m);

                if (name.Length > maxName) { name = name.Substring(0, maxName); }
                if (address.Length > maxAddress) { address = address.Substring(0, maxAddress); }
                if (contact.Length > maxContact) { contact = contact.Substring(0, maxContact); }
                Assert.AreEqual(tName, u.GetType().Name);
                Assert.AreEqual(id, u.GetId());
                Assert.AreEqual(name, u.Name);
                Assert.AreEqual(address, u.Address);
                Assert.AreEqual(contact, u.Contact);
            }

        }

        /// <summary>
        /// Tests MemberManager.LoadEntities(), PersistEntities().
        /// </summary>
        [TestMethod]
        public void TestLoadPersist()
        {
            // entities to persist
            int entities = 100;
            Random r = new Random();
            TestableMemberManager tlmP = new TestableMemberManager();
            MemberManager lm = new MemberManager();

            Int32 id;
            string name;
            var maxName = MemberManager.MAX_NAME_LENGTH;
            string address;
            var maxAddress = MemberManager.MAX_ADDR_LENGTH;
            string contact;
            var maxContact = MemberManager.MAX_CONTACT_LENGTH;

            // generate random entities
            var randUniStr = new RandomUnicodeString();
            Member mb = new Other();
            string tName;
            byte[] m;
            Member u;
            for (int i = 0; i < entities; i++)
            {
                id = r.Next(TestableMemberManager.FirstId, Int32.MaxValue);
                name = randUniStr.GetString(r.Next(maxName * 2));
                address = randUniStr.GetString(r.Next(maxAddress * 2));
                contact = randUniStr.GetString(r.Next(maxContact * 2));

                switch (i % 4 + 1)
                {
                    case 1:
                        mb = new LocalStudent(id, name, address, contact);
                        break;
                    case 2:
                        mb = new LocalTeacher(id, name, address, contact);
                        break;
                    case 3:
                        mb = new ForeignUniMember(id, name, address, contact);
                        break;
                    case 4:
                        mb = new Other(id, name, address, contact);
                        break;
                }
                tName = mb.GetType().Name;
                m = lm.Marshal(mb);
                u = lm.Unmarshal(m);

                // place the entity into one of the managed lists, based on i's value
                tlmP.Managed.Add(mb);
                switch (i % 4)
                {
                    case 0:
                        tlmP.Entities.Add(mb);
                        break;

                    case 1:
                        tlmP.Edited.Add(mb);
                        break;

                    case 2:
                        tlmP.New.Add(mb);
                        break;

                    case 3:
                        tlmP.Deleted.Add(mb);
                        break;
                }
            }

            // persist the entities & wait for the os
            tlmP.PersistEntities();
            Thread.Sleep(1000);

            // load them back
            TestableMemberManager tlmL = new TestableMemberManager();
            tlmL.LoadEntities();

            // test the results
            //

            // list counts
            Assert.AreEqual(tlmP.Managed.Count - tlmP.Deleted.Count, tlmL.Managed.Count);
            Assert.AreEqual(tlmP.Entities.Count + tlmP.Edited.Count + tlmP.New.Count, tlmL.Entities.Count);
            Assert.AreEqual(0, tlmL.Edited.Count);
            Assert.AreEqual(0, tlmL.New.Count);
            Assert.AreEqual(0, tlmL.Deleted.Count);

            // list values
            foreach (Member l in tlmP.Entities)
            {
                bool found = false;
                foreach (Member o in tlmL.Entities)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }
            foreach (Member l in tlmP.Edited)
            {
                bool found = false;
                foreach (Member o in tlmL.Entities)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }
            foreach (Member l in tlmP.New)
            {
                bool found = false;
                foreach (Member o in tlmL.Entities)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }

            var list = tlmP.Managed.Where(item => !(tlmP.Deleted.Contains(item))).ToList();
            foreach (Member l in tlmL.Managed)
            {
                bool found = false;
                foreach (Member o in list)
                {
                    var ml = tlmP.Marshal(l);
                    var mo = tlmP.Marshal(o);
                    if (ml.SequenceEqual(mo)) { found = true; }
                }

                Assert.IsTrue(found);
            }

            // delete the file
            File.Delete(tlmP.GetDatafileName());
        }

    }
}
